/*
* Implement lexical analysis
*/

#include "../inc/config.h"
#include "../inc/regex_digit.h"
#include "../inc/lexer.h"
#include "../inc/log.h"

char *filename;
Link **hashmap;
Queue * qfile;
const char *input;
long size;

const size_t nmatch = 1;
regmatch_t pmatch[1];
regex_t reg_digit;
regex_t reg_int;
regex_t reg_u;
regex_t reg_l;
regex_t reg_ul;
regex_t reg_ll;
regex_t reg_ull;
regex_t reg_oct;
regex_t reg_hex;
regex_t reg_bin;
regex_t reg_float;
regex_t reg_exp;
const char* pattern_digit = "[+-]?[0-9]?\\.?e?u?l?b?x?[a-f]?[p]?"; 
const char* pattern_int = "[+-]?[0-9]+"; 
const char* pattern_u = "[+-]?[0-9]+u"; 
const char* pattern_l = "[+-]?[0-9]+l"; 
const char* pattern_ul = "[0-9]+(ul|lu)"; 
const char* pattern_ll = "[+-]?[0-9]+l{1,2}"; 
const char* pattern_ull = "[0-9]+(ull|llu)"; 
const char* pattern_oct = "0[1-7]+(f|l|u|lu|ul|ll|llu|ull)?"; 
const char* pattern_hex = "[+-]?0x[0-9a-f]*\\.?[0-9a-f]+p?[+-]?[0-9]*(f|l|u|lu|ul|ll|llu|ull)?";
const char* pattern_bin = "0b[0-1]+(f|l|u|lu|ul|ll|llu|ull)?"; 
const char* pattern_float = "[+-]?[0-9]*\\.[0-9]*(l|f)?";
const char* pattern_exp = "[+-]?[0-9]*\\.?[0-9]*[eE][+-]?[0-9]+(l|f)?"; 

/* Initialize syntax analysis related variables and regular expressions */
void init_lex(Queue *q, Link** link, const char* content, long length, char* file){
	hashmap = link;
	input = content;
	filename = file;
	qfile = q;
	size = length - 1;
	// write_log(DEBUG,__FILE__, "content size %d\n", size);
	if(init_regex(&reg_digit, pattern_digit)){
		_log(NONE,__FILE__, "pattern_digit regex compile failed.");
	}
	if(init_regex(&reg_int, pattern_int)){
		_log(NONE,__FILE__, "pattern_int regex compile failed.");
	}
	if(init_regex(&reg_u, pattern_u)){
		_log(NONE,__FILE__, "pattern_u regex compile failed.");
	}
	if(init_regex(&reg_ul, pattern_ul)){
		_log(NONE,__FILE__, "pattern_ul regex compile failed.");
	}
	if(init_regex(&reg_l, pattern_l)){
		_log(NONE,__FILE__, "pattern_l regex compile failed.");
	}
	if(init_regex(&reg_ll, pattern_ll)){
		_log(NONE,__FILE__, "pattern_ll regex compile failed.");
	}
	if(init_regex(&reg_ull, pattern_ull)){
		_log(NONE,__FILE__, "pattern_ull regex compile failed.");
	}
	if(init_regex(&reg_oct, pattern_oct)){
		_log(NONE,__FILE__, "pattern_oct regex compile failed.");
	}
	if(init_regex(&reg_hex, pattern_hex)){
		_log(NONE,__FILE__, "pattern_hex regex compile failed.");
	}
	if(init_regex(&reg_bin, pattern_bin)){
		_log(NONE,__FILE__, "pattern_bin regex compile failed.");
	}
	if(init_regex(&reg_float, pattern_float)){
		_log(NONE,__FILE__, "pattern_float regex compile failed.");
	}
	if(init_regex(&reg_exp, pattern_exp)){
		_log(NONE,__FILE__, "pattern_exp regex compile failed.");
	}
}

/* free the regular expressions */
void clear_lex(){
	clear_regex(&reg_digit);
	clear_regex(&reg_int);
	clear_regex(&reg_u);
	clear_regex(&reg_l);
	clear_regex(&reg_ul);
	clear_regex(&reg_ll);
	clear_regex(&reg_ull);
	clear_regex(&reg_oct);
	clear_regex(&reg_hex);
	clear_regex(&reg_bin);
	clear_regex(&reg_float);
	clear_regex(&reg_exp);
}

/* free the token */
void clear_token(Token *t){
	t->type = UNRECOGNIZED;
	t->next_type = -1;
	t->pos_begin = 0;
	t->pos_end = 0;
	for(int i = 0; i < TOKEN_MAX_SIZE; ++i){
		t->token[i] = '\0';
	}
}

/* check whether the starting position of the file */
int is_begin(Token *t){
	return t->pos_end == 0;
}

/* check whether the end position of the file */
int is_end(Token *t){
	return t->pos_end == size;
}

/* pointer move to next position */
void next_char(Token *t){
	if(is_end(t)) return;
	else
		t->pos_end++;
}

/* pointer skip whitespace*/
void skip_whitespace(Token *t){
	if(is_end(t)) return;
	else{
		t->pos_begin++;
		t->pos_end++;
	}
}

/*pointer backward one position*/
void retract(Token *t){
	if(is_begin(t)) return;
	else
	 t->pos_end--;
}

/* combined characters into a string */
int concat_token(Token *t){
	int size = t->pos_end - t->pos_begin + 1;
	if (size > 1024) {
		file_log(qfile, FATAL, filename, "error: identifier length > 1024");
		return 0;
	}
	for(int i = 0; i < size; ++i){
		t->token[i] = input[t->pos_begin + i];
	}
	t->token[size] = '\0';
	return 1;
}

/*skip the spaces in token and then combined chars into a string */
int concat_token_withspace(int head, Token *t){
	int size = t->pos_end - t->pos_begin + 1;
	if (size > 1024) {
		file_log(qfile, FATAL, filename, "error: identifier length > 1024");
		return 0;
	}
	t->token[0] = input[head];
	for(int i = 1; i < size; ++i){
		t->token[i] = input[t->pos_begin + i];
	}
	t->token[size] = '\0';
	t->pos_begin = head;
	return 1;
}

/*check whether the beginning of importing external filename */
int is_lheader(int curpos){
	if(input[curpos] == '<') return 1;
	if(input[curpos] == '"') return 2;
	return 0;
}

/*check whether the end of importing external filename */
int is_rheader(int curpos, int type){
	if(type == 1 && input[curpos] == '>') return 1;
	if(type == 2 && input[curpos] == '"') return 2;
	return 0;
}

/*check whether importing external filename */
int is_headerfile(int curpos){
	return input[curpos - 2] == '.' && (input[curpos - 1] == 'h' || input[curpos - 1] == 'c');
}

/*skip the space, carriage return */
int is_whitespace(int curpos){
	return input[curpos] == ' ' || input[curpos] == '\t' || input[curpos] == '\v'
		|| input[curpos] == '\f' || input[curpos] == '\r';
}

/* If it's a line break, add one */
int is_newline_add(int curpos, int* pline){
	if(input[curpos] == '\n') (*pline)++;
    return input[curpos] == '\n';
} 

/* check whether is a line break */
int is_newline(int curpos){
    return input[curpos] == '\n';
} 

/* check whether is a string */
int is_string(Token *t, int* pline){
	if(input[t->pos_begin] == '"'){
		t->pos_end = t->pos_begin;
		next_char(t);
		int flag = 0;
		while(!is_end(t)){
			//skip '\'+'\n'
			while(input[t->pos_end] == '\\' && input[t->pos_end + 1] == '\n'){
				next_char(t);
				is_newline_add(t->pos_end, pline);
				next_char(t);
			}
			//The number of consecutive backstash is even
			if(input[t->pos_end] == '"'){
				if(input[t->pos_end - 1] != '\\') {flag = 1; break;}
				else{
					int cur = t->pos_end - 1;
					int backslash = 0;
					while(input[cur] == '\\') {backslash++; cur--;}
					if(backslash % 2 == 0) {flag = 1; break;}
				}
			}
			next_char(t);
		}
		if(flag){
			concat_token(t);
			return 1;
		}else{
			return 0;
		}
	}
	return 0;
}

/* check whether is '#' */
int is_poundkey(int curpos){
	return input[curpos] == '#';
}

/* check whether is '#', '#@', '##' */
int is_marco(Token *t){
	if(input[t->pos_end] == '#' && input[t->pos_end + 1] == '#') {t->pos_end++; return 1;}
	else if(input[t->pos_end] == '#' && input[t->pos_end + 1] == '@') {t->pos_end++; return 1;}
	else if(input[t->pos_end] == '#' ) return 1;
	else return 0;
}

/* check whether is a lowercase letter */
int is_lower(int curpos){
	return islower(input[curpos]);
}

/* check whether is a letter , '_' and '$'  */
int is_letter(int curpos){
	return input[curpos] =='_' || input[curpos] =='$' || isalpha(input[curpos]);
}

/* check whether is a digit */
int is_digit(int curpos){
	return isdigit(input[curpos]);
}

/* check whether is a char */
int is_char(int curpos, Token *t){
	if(input[curpos] != '\'') return 0;
	// 'x'
	if (curpos + 2 <= size && input[curpos + 1] != '\\'
		&& input[curpos + 2] == '\'') {t->pos_end += 2; return 1;}
	// '\x' '\0'
	if (curpos + 3 <= size && input[curpos + 1] == '\\' 
		&& input[curpos + 3] == '\'') {t->pos_end += 3; return 1;}
	// oct: '\000' "[0-7]+"  hex: '\xhh' "x[0-9a-f]+" '\00' or '\xh'
	if (curpos + 4 <= size && input[curpos + 1] == '\\' 
		&& input[curpos + 4] == '\'') {t->pos_end += 4; return 1;}
	// '\000' or '\xhh'
	if (curpos + 5 <= size && input[curpos + 1] == '\\' 
		&& input[curpos + 5] == '\'') {t->pos_end += 5; return 1;}
	return 0;
}

/* check whether is a NULL */
int is_null(Token *t){
	return !strcmp("NULL", t->token);
}

/* check whether is a bool type */
int is_bool(Token *t){
	return !strcmp("TRUE", t->token) || !strcmp("FALSE", t->token);
}

/* check whether is a keyword */
int is_keyword(Token *t) {
	return search(hashmap, t->token) == KEYWORDS;
}

/* check whether is a preprocess */
int is_preprocess(Token *t) {
	return search(hashmap, t->token) == PREPROCESS;
}

/* check whether is a deliiter */
int is_delimiter(int curpos) {
	char str[2];
	str[0] = input[curpos];
	str[1] = '\0';
	return search(hashmap, str) == DELIMITER;
}

/* omit too lenght string */
void omit_str(char *str, Token *t){
	str[0] = '"';
	for(int i = 1; i < 16; ++i){
		str[i] = t->token[i];
	}
	str[16] = '.';
	str[17] = '.';
	str[18] = '.';
	str[19] = '"';
	str[20] = '\0'; 
}

/* check whether is a number, include int, float, double */
int is_number(int curpos){
	return input[curpos] == '+' || input[curpos] == '-' || input[curpos] == '.'
	|| is_digit(curpos);
}

/* check whether is a single operator */
int is_single_operator(int curpos){
	char str[2];
	str[0] = input[curpos];
	str[1] = '\0';
	return search(hashmap, str) == OPERATOR;
}

/* check whether is a operator */
int is_operator(int curpos){
	int flag = !(input[curpos] == '?' || input[curpos] == ':' || input[curpos] == '.');
	return flag && input[curpos + 1] == '=';
}

/* check whether is a double operator */
int is_double_operator(int curpos){
	return (input[curpos] == '|' && input[curpos + 1] == '|') ||
	(input[curpos] == '&' && input[curpos + 1] == '&') ||
	(input[curpos] == '-' && input[curpos + 1] == '-') ||
	(input[curpos] == '+' && input[curpos + 1] == '+') ||
	(input[curpos] == '<' && input[curpos + 1] == '<') ||
	(input[curpos] == '>' && input[curpos + 1] == '>') ||
	(input[curpos] == '*' && input[curpos + 1] == '*') ||
	(input[curpos] == '-' && input[curpos + 1] == '>');

}

/* check whether is a triple operator */
int is_triple_operator(int curpos){
	return (input[curpos] == '<' && input[curpos + 1] == '<' && input[curpos + 2] == '=') ||
	(input[curpos] == '>' && input[curpos + 1] == '>' && input[curpos + 2] == '=') ||
	(input[curpos] == '.' && input[curpos + 1] == '.' && input[curpos + 2] == '.');
}

/* check whether is not a number */
int is_no_number(int curpos){
	char str[2];
	str[0] = input[curpos];
	str[1] = '\0';
	return !(1 == regex_match(&reg_digit, str, nmatch, pmatch));
}

/* getting the type of number */
int get_number_type(Token *t){
	int offset = t->pos_end - t->pos_begin + 1;
	if(offset == regex_match(&reg_oct, t->token, nmatch, pmatch)){
		t->type = CONST_INT_OCTAL;
		return 1;
	}
	if(offset == regex_match(&reg_int, t->token, nmatch, pmatch)){
		t->type = CONST_INT;
		return 1;
	}
	if(offset == regex_match(&reg_u, t->token, nmatch, pmatch)){
		t->type = CONST_INT_U;
		return 1;
	}
	if(offset == regex_match(&reg_ul, t->token, nmatch, pmatch)){
		t->type = CONST_INT_UL;
		return 1;
	}
	if(offset == regex_match(&reg_l, t->token, nmatch, pmatch)){
		t->type = CONST_INT_L;
		return 1;
	}
	if(offset == regex_match(&reg_ll, t->token, nmatch, pmatch)){
		t->type = CONST_INT_LL;
		return 1;
	}
	if(offset == regex_match(&reg_ull, t->token, nmatch, pmatch)){
		t->type = CONST_INT_ULL;
		return 1;
	}
	if(offset == regex_match(&reg_hex, t->token, nmatch, pmatch)){
		t->type = CONST_INT_HEX;
		return 1;
	}
	if(offset == regex_match(&reg_bin, t->token, nmatch, pmatch)){
		t->type = CONST_INT_BIN;
		return 1;
	}
	if(offset == regex_match(&reg_float, t->token, nmatch, pmatch)){
		t->type = CONST_FLOAT;
		return 1;
	}
	if(offset == regex_match(&reg_exp, t->token, nmatch, pmatch)){
		t->type = CONST_FLOAT_EXP;
		return 1;
	}
	return 0;
}

/* getting the type of digit */
int get_digit_type(Token* t){
	if(get_number_type(t)){
		return 1;
	}else{
		// search for '+', '-' in the backward position
		int begin = t->pos_begin + 1;
		int end = t->pos_end;
		while(begin <= end){
			if(input[begin] == '+' || input[begin] == '-'){
				t->pos_end = begin - 1;
				break;
			}
			begin++;
	}
	concat_token(t);
	return get_number_type(t);
	}
}

/* check whether is a single line comment */
int is_single_comment(int curpos){
	return input[curpos] == '/' && input[curpos + 1] == '/';
}

/* check whether is beginning of multiple line comment */
int is_lcomment(int curpos){
	return input[curpos] == '/' && input[curpos + 1] == '*';
}

/* check whether is end of multiple line comment */
int is_rcomment(int curpos){
	return input[curpos] == '/' && input[curpos - 1] == '*';
}

/* analyze the syntax type of each token */
enum symbol_type get_symbol(Token *t, int curpos, int line, int printflag){
	if(t->next_type == OPERATOR){
		t->type = OPERATOR;
		t->next_type = -1;
		t->pos_end++;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
		}
		return t->type;
	}
	clear_token(t);
	t->pos_begin = curpos;
	t->pos_end = curpos;
	// skip white space
	while(is_newline_add(t->pos_end, &line) || is_whitespace(t->pos_end)){
		skip_whitespace(t);
		if(is_end(t)) break;
	}
	// is the end of file
	if(is_end(t)){
		t->type = FILE_EOF;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", "EOF", get_symbol_type(t->type));
		}
		return t->type;
	}
	// recognize preprocess
	if(is_poundkey(t->pos_end)){
		int head = t->pos_begin;
		int spaceflag = 0;
		next_char(t);
		if(is_whitespace(t->pos_end)){
			spaceflag = 1;
			while(is_whitespace(t->pos_end)) skip_whitespace(t);
		}
		retract(t);
		do{
			next_char(t);
			if(is_newline(t->pos_end) || is_end(t)) break;
		}while(is_lower(t->pos_end));
		if(!is_lower(t->pos_end) || is_newline(t->pos_end)) retract(t);
		if(spaceflag){
			if(!concat_token_withspace(head, t)) return ERROR;
		}else{
			if(!concat_token(t)) return ERROR;
		}
		if(is_preprocess(t)){
			t->type = PREPROCESS;
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			} 
			return t->type;
		}else{
			t->pos_end = head;
			if(is_marco(t)){
				concat_token(t);
				t->type = MACRO;
				if(printflag){
					file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
				} 
				return t->type;
			} 
		}
	}
	// recognize header file
	if(is_lheader(t->pos_end) > 0){
		int smallbracket = is_lheader(t->pos_end);
		do{
			next_char(t);
			if(is_newline(t->pos_end) || is_end(t)) break;
		}while(!is_rheader(t->pos_end, smallbracket));
		if(!concat_token(t)) return ERROR;
		if(is_headerfile(t->pos_end)){
			t->type = HEADER;
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			}
			return t->type;
		}
		// recognize string
		if((2 == smallbracket) && is_string(t, &line)){
			t->type = CONST_STR;
			int size = t->pos_end - t->pos_begin + 1;
			if(size > 17){
				char tmp[21];
				omit_str(tmp, t);
				if(printflag){
					file_log(qfile, DEBUG, filename, "%-20s : %s", tmp, get_symbol_type(t->type));
				} 
			}else{
				if(printflag){
					file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
				} 
			}
			return t->type;
		}
		t->pos_end = t->pos_begin;
	}
	// recognize identifier, keywords, bool, null
	if(is_letter(t->pos_end)){
		do{
			next_char(t);
			if(is_newline(t->pos_end) || is_end(t)) break;
		}while(is_letter(t->pos_end) || is_digit(t->pos_end));
		if(is_newline(t->pos_end) || (!is_letter(t->pos_end) && !is_digit(t->pos_end))) retract(t);
		if(!concat_token(t)) return ERROR;
		// recognize keywords
		if(is_keyword(t)){
			t->type = KEYWORDS;
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			} 
			return t->type;
		}else if(is_bool(t)){
			t->type = CONST_BOOL;
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			}
			return t->type;
		}else if(is_null(t)){
			t->type = CONST_NULL;
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			}
			return t->type;
		}else{
			t->type = IDENTIFIER;
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			}
			// check next token if is '('
			if(t->pos_end < size){
				int next_pos = t->pos_end + 1;
				while(is_whitespace(next_pos) || is_newline(next_pos)) next_pos++;
				if(next_pos <= size && input[next_pos] == '(') t->next_type = FUNCTION;
			}
			return t->type;
		}
	}
	// recognize digit by regular matching
	if(is_number(t->pos_end)){
		while(!is_no_number(t->pos_end)){
			next_char(t);
			if(is_newline(t->pos_end) || is_end(t)) break;
		}
		if(is_newline(t->pos_end) || (is_no_number(t->pos_end))) retract(t);
		if(!concat_token(t)) return ERROR;
		if(get_digit_type(t)){
			if(printflag){
				file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
			}
			if(t->pos_end < size && (input[t->pos_end + 1] == '+' || input[t->pos_end + 1] == '-')){
				t->next_type = OPERATOR;
			}
			return t->type;
		}else{
			t->pos_end = t->pos_begin;
		}
	}
	// recognize delimiter
	if(is_delimiter(t->pos_end)){
		concat_token(t);
		t->type = DELIMITER;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
		}
		return t->type;
	}
	// recognize comment
	if(is_single_comment(t->pos_end)){
		do{
			next_char(t);
			if(is_end(t)) break;
		}while(!is_newline_add(t->pos_end, &line));
		t->type = COMMENT;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", "//", get_symbol_type(t->type));
		} 
		return t->type;
	}
	if(is_lcomment(t->pos_end)){
		do{
			next_char(t);
			if(is_end(t)) break;
		}while(!is_rcomment(t->pos_end));
		t->type = COMMENT;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", "/*...*/", get_symbol_type(t->type));
		}
		return t->type;
	}
	// recognize char
	if(is_char(t->pos_end, t)){
		if(!concat_token(t)) return ERROR;
		t->type = CONST_CHAR;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
		} 
		return t->type;
	}
	// recognize operator
	if(is_single_operator(t->pos_end)){
		if(is_double_operator(t->pos_end) || is_operator(t->pos_end)){
			next_char(t);
		}else if(is_triple_operator(t->pos_end)){
			next_char(t);
			next_char(t);
		}
		concat_token(t);
		t->type = OPERATOR;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
		}
		return t->type;
	}
	// check wether is blackslash
	if(!concat_token(t)) return ERROR;
	if(input[t->pos_end] == '\\' && input[t->pos_end + 1] == '\n'){
		t->type = BACKSLASH;
		if(printflag){
			file_log(qfile, DEBUG, filename, "%-20s : %s", t->token, get_symbol_type(t->type));
		} 
		return t->type;
	}
	// unrecognize symbol
	t->type = UNRECOGNIZED;
	if(printflag){
		file_log(qfile, DEBUG, filename, "%-20s : %s:%d", t->token, get_symbol_type(t->type), line);
	} 
	return t->type;
}
